/*****************************************/
/* File: yvm.c                                                       */
/* Yueq Virtual Machine Emulator                             */
/* Author : K.C.L                                                   */
/* Further Modified by Qi Yue (yueq@cse.buaa.edu.cn)*/
/*****************************************/

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

/******* const *******/
#define   IADDR_SIZE  1024 /* increase for large programs */
#define   DADDR_SIZE  1024 /* increase for large programs */
#define   NO_REGS 8
#define   PC_REG  7

#define   LINESIZE  121
#define   WORDSIZE  20

/******* type  *******/

typedef enum {
   opclRR,     /* reg operands r,s,t */
   opclRM,     /* reg r, mem d+s */
   opclRA      /* reg r, int d+s */
   } OPCLASS;

typedef enum {
   /* RR instructions */
   opHALT,    /* RR     halt, operands are ignored */
   opIN,      /* RR     read into reg(r); s and t are ignored */
   opOUT,     /* RR     write from reg(r), s and t are ignored */
   opADD,    /* RR     reg(r) = reg(s)+reg(t) */
   opSUB,    /* RR     reg(r) = reg(s)-reg(t) */
   opMUL,    /* RR     reg(r) = reg(s)*reg(t) */
   opDIV,    /* RR     reg(r) = reg(s)/reg(t) */
   opRRLim,   /* limit of RR opcodes */

   /* RM instructions */
   opLD,      /* RM     reg(r) = mem(d+reg(s)) */
   opST,      /* RM     mem(d+reg(s)) = reg(r) */
   opRMLim,   /* Limit of RM opcodes */

   /* RA instructions */
   opLDA,     /* RA     reg(r) = d+reg(s) */
   opLDC,     /* RA     reg(r) = d ; reg(s) is ignored */
   opJLT,     /* RA     if reg(r)<0 then reg(7) = d+reg(s) */
   opJLE,     /* RA     if reg(r)<=0 then reg(7) = d+reg(s) */
   opJGT,     /* RA     if reg(r)>0 then reg(7) = d+reg(s) */
   opJGE,     /* RA     if reg(r)>=0 then reg(7) = d+reg(s) */
   opJEQ,     /* RA     if reg(r)==0 then reg(7) = d+reg(s) */
   opJNE,     /* RA     if reg(r)!=0 then reg(7) = d+reg(s) */
   opRALim    /* Limit of RA opcodes */
   } OPCODE;

typedef enum {
   srOKAY,
   srHALT,
   srIMEM_ERR,
   srDMEM_ERR,
   srZERODIVIDE
   } STEPRESULT;

typedef struct {
      int iop  ;
      int iarg1  ;
      int iarg2  ;
      int iarg3  ;
   } INSTRUCTION;

/******** vars ********/
int iloc = 0 ;
int dloc = 0 ;
int traceflag = FALSE;
int icountflag = FALSE;

INSTRUCTION iMem [IADDR_SIZE];
int dMem [DADDR_SIZE];
int reg [NO_REGS];

char *opCodeTab[] = 
{"HALT","IN","OUT","ADD","SUB","MUL","DIV","????", /* RR opcodes */
 "LD","ST","????", /* RM opcodes */
 "LDA","LDC","JLT","JLE","JGT","JGE","JEQ","JNE","????" /* RA opcodes */
};

char *stepResultTab[] =
{"OK",
 "Halted",
 "Instruction Memory Fault",
 "Data Memory Fault",
 "Division by 0"
};

char pgmName[20];
FILE *pgm;

char in_Line[LINESIZE];
int lineLen;
int inCol;
int num;		//int num
char word[WORDSIZE];
char ch;
int done;

/********************************************/
int opClass( int c )
{
	if ( c <= opRRLim) 
		return ( opclRR );
	else if ( c <= opRMLim)
		return ( opclRM );
	else
		return ( opclRA );
} /* opClass */

/********************************************/
void writeInstruction ( int loc )
{ 
	printf( "%5d: ", loc) ;
	if ( (loc >= 0) && (loc < IADDR_SIZE) )
	{
		printf("%6s%3d,", opCodeTab[iMem[loc].iop], iMem[loc].iarg1);
		switch ( opClass(iMem[loc].iop) )
		{
			case opclRR: 
				printf("%1d,%1d", iMem[loc].iarg2, iMem[loc].iarg3);
				break;
			case opclRM:
			case opclRA: 
				printf("%3d(%1d)", iMem[loc].iarg2, iMem[loc].iarg3);
      			             break;
		}
		printf ("\n") ;
	}
} /* writeInstruction */

/********************************************/
void getCh (void)
{ 
	if (++inCol < lineLen)
		ch = in_Line[inCol] ;
	else 
		ch = ' ' ;
} /* getCh */

/********************************************/
int nonBlank (void)
{ 
	while ((inCol < lineLen)&& (in_Line[inCol] == ' ') )
		inCol++ ;
	if (inCol < lineLen)
	{
		ch = in_Line[inCol] ;
		return TRUE ; 
	}
	else
	{
		ch = ' ' ;
		return FALSE ; 
	}
} /* nonBlank */

/********************************************/
int getNum (void)
{
	int sign = 1;
	int term;
	int temp = FALSE;
	int is_float = FALSE;
	int float_num = 0;
	int i = 0;
	num = 0 ;
	do
	{
		sign = 1;
		while ( nonBlank() && ((ch == '+') || (ch == '-') || (ch == '-')) )
		{
			temp = FALSE ;
		      if (ch == '-')
			  	sign =  -1 *sign ;
			getCh();
		}
		
		term = 0 ;
		nonBlank();

		if (!is_float)
			while (isdigit(ch))
			{
				temp = TRUE ;
				term = term *10 + ( ch - '0' ) ;
				getCh();
			}
		else
		{
			while (isdigit(ch))
			{
				temp = TRUE;
				term = term * 10 + (ch - '0');
				getCh();
				float_num++;
			}
		}
		num = num + (term *sign);
	  } while ( (nonBlank()) && ((ch == '+') || (ch == '-') || (ch == '.')) ) ;
	  return temp;
} /* getNum */

/********************************************/
int getWord (void)
{
	int temp = FALSE;
	int length = 0;
	if (nonBlank ())
	{
		while (isalnum(ch))
		{
			if (length < WORDSIZE-1) word [length++] =  ch ;
			getCh() ;
		}
		word[length] = '\0';
		temp = (length != 0);
	}

	return temp;
} /* getWord */

/********************************************/
int skipCh ( char c  )
{
	int temp = FALSE;
	if ( nonBlank() && (ch == c) )
	{ 
		getCh();
		temp = TRUE;
	}
	return temp;
} /* skipCh */

/********************************************/
int atEOL(void)
{ 
	return ( ! nonBlank ());
} /* atEOL */

/********************************************/
int error( char *msg, int lineNo, int instNo)
{ 
	printf("Line %d",lineNo);
	if (instNo >= 0) 
		printf(" (Instruction %d)",instNo);
	printf("   %s\n",msg);
	return FALSE;
} /* error */

/********************************************/
int readInstructions (void)
{
	OPCODE op;
	int arg1, arg2, arg3;
	int loc, regNo, lineNo;

	for (regNo = 0 ; regNo < NO_REGS ; regNo++)
		reg[regNo] = 0 ;
	dMem[0] = DADDR_SIZE - 1 ;

	for (loc = 1 ; loc < DADDR_SIZE ; loc++)
		dMem[loc] = 0 ;
	for (loc = 0 ; loc < IADDR_SIZE ; loc++)
	{
		iMem[loc].iop = opHALT ;
		iMem[loc].iarg1 = 0 ;
		iMem[loc].iarg2 = 0 ;
		iMem[loc].iarg3 = 0 ;
	}
	lineNo = 0 ;
	while (!feof(pgm))
	{ 
		fgets( in_Line, LINESIZE-2, pgm  ) ;
		inCol = 0 ; 
		lineNo++;
		lineLen = strlen(in_Line)-1 ;

		if (in_Line[lineLen]=='\n') 
			in_Line[lineLen] = '\0' ;
		else
			in_Line[++lineLen] = '\0';
	if ( (nonBlank()) && (in_Line[inCol] != ';') )
	{
		if (! getNum())
			return error("Bad location", lineNo,-1);
	  	loc = num;
		
		if (loc > IADDR_SIZE)
			return error("Location too large",lineNo,loc);
		if (! skipCh(':'))
			return error("Missing colon", lineNo,loc);
		if (! getWord ())
			return error("Missing opcode", lineNo,loc);

		op = opHALT ;
		while ((op < opRALim) && (strncmp(opCodeTab[op], word, 4) != 0) )
		      op++ ;
		if (strncmp(opCodeTab[op], word, 4) != 0)
			return error("Illegal opcode", lineNo,loc);

		switch ( opClass(op) )
		{ 
		case opclRR :
		/***********************************/
			if ( (! getNum ()) || (num < 0) || (num >= NO_REGS) )
				return error("Bad first register", lineNo,loc);
			arg1 = num;
			if ( ! skipCh(','))
				return error("Missing comma", lineNo, loc);
			if ( (! getNum ()) || (num < 0) || (num >= NO_REGS) )
				return error("Bad second register", lineNo, loc);
			arg2 = num;
			if ( ! skipCh(',')) 
				return error("Missing comma", lineNo,loc);
			if ( (! getNum ()) || (num < 0) || (num >= NO_REGS) )
				return error("Bad third register", lineNo,loc);
			arg3 = num;
		break;

		case opclRM :
		case opclRA :
		/***********************************/
			if ( (! getNum ()) || (num < 0) || (num >= NO_REGS) )
				return error("Bad first register", lineNo,loc);
			arg1 = num;
			if ( ! skipCh(','))
				return error("Missing comma", lineNo,loc);
			if (! getNum ())
				return error("Bad displacement", lineNo,loc);
			arg2 = num;
			if ( ! skipCh('(') && ! skipCh(',') )
				return error("Missing LParen", lineNo,loc);
			if ( (! getNum ()) || (num < 0) || (num >= NO_REGS))
				return error("Bad second register", lineNo,loc);
			arg3 = num;
		break;
		}
		  iMem[loc].iop = op;
		  iMem[loc].iarg1 = arg1;
		  iMem[loc].iarg2 = arg2;
		  iMem[loc].iarg3 = arg3;
		}
	}
	return TRUE;
} /* readInstructions */


/********************************************/
STEPRESULT stepYVM (void)
{
	INSTRUCTION currentinstruction  ;
	int pc  ;
	int r,s,t,m  ;
	int ok ;

	pc = reg[PC_REG] ;
	if ( (pc < 0) || (pc > IADDR_SIZE)  )
		return srIMEM_ERR ;
	reg[PC_REG] = pc + 1 ;
	currentinstruction = iMem[ pc ] ;
	switch (opClass(currentinstruction.iop) )
	{
	case opclRR :
    /***********************************/
	      r = currentinstruction.iarg1 ;
	      s = currentinstruction.iarg2 ;
	      t = currentinstruction.iarg3 ;
	break;

	case opclRM :
    /***********************************/
	      r = currentinstruction.iarg1 ;
	      s = currentinstruction.iarg3 ;
	      m = currentinstruction.iarg2 + reg[s] ;
	      if ( (m < 0) || (m > DADDR_SIZE))
	         return srDMEM_ERR ;
      break;

    case opclRA :
    /***********************************/
	      r = currentinstruction.iarg1 ;
	      s = currentinstruction.iarg3 ;
	      m = currentinstruction.iarg2 + reg[s] ;
      break;
  } /* case */

	switch ( currentinstruction.iop)
	{ /* RR instructions */
    	case opHALT :
    /***********************************/
	      printf("HALT: %1d,%1d,%1d\n",r,s,t);
	      return srHALT ;
      /* break; */

	case opIN :
    /***********************************/
	      do
	      { 
			printf("Enter value for IN instruction: ") ;
			fflush (stdin);
			fflush (stdout);
			fgets(in_Line,200,stdin);
			lineLen = strlen(in_Line) ;
			inCol = 0;
			ok = getNum();
			if (!ok)
				printf ("Illegal value\n");
			else
				reg[r] = num;
	      }while (! ok);
      break;

	case opOUT :  
		printf ("OUT instruction prints: %d\n", reg[r] ) ;
      break;
	case opADD :
		reg[r] = reg[s] + reg[t] ; break;
	case opSUB :  
		reg[r] = reg[s] - reg[t] ; break;
	case opMUL : 
		reg[r] = reg[s] *reg[t] ; break;

	case opDIV :
    /***********************************/
		if (reg[t] != 0 )
			reg[r] = reg[s] / reg[t];
		else
			return srZERODIVIDE ;
      break;

    /*************** RM instructions ********************/
	case opLD :
		reg[r] = dMem[m]; break;
	case opST :
		dMem[m] = reg[r]; break;

    /*************** RA instructions ********************/
	case opLDA :
		reg[r] = m; break;
	case opLDC :
		reg[r] = currentinstruction.iarg2; break;
	case opJLT :
		if (reg[r] <  0) reg[PC_REG] = m; break;
	case opJLE :
		if (reg[r] <=  0) reg[PC_REG] = m; break;
	case opJGT :
		if (reg[r] >  0) reg[PC_REG] = m; break;
	case opJGE :    
		if (reg[r] >=  0) reg[PC_REG] = m ; break;
	case opJEQ :    
		if (reg[r] == 0) reg[PC_REG] = m ; break;
	case opJNE :
		if (reg[r] != 0) reg[PC_REG] = m ; break;

    /* end of legal instructions */
  } /* case */
	return srOKAY ;
} /* stepYVM */

/********************************************/
int doCommand (void)
{ 
	char cmd;
	int stepcnt=0, i;
	int printcnt;
	int stepResult;
	int regNo, loc;
	do
	{ 
		printf ("Enter command: ");
		fflush (stdin);
		fflush (stdout);
		fgets(in_Line,200,stdin);
		lineLen = strlen(in_Line);
		inCol = 0;
	} while (! getWord ());

	cmd = word[0] ;
	switch ( cmd )
	{
	case 't' :
    /***********************************/
		traceflag = ! traceflag ;
	      printf("Tracing now ");
	      if ( traceflag ) printf("on.\n");
			else printf("off.\n");
      break;

	case 'h' :
	/***********************************/
	printf("Commands are:\n");
	printf("   s(tep <n>      "\
	     "Execute n (default 1) YVM instructions\n");
	printf("   g(o            "\
	     "Execute YVM instructions until HALT\n");
	printf("   r(egs          "\
	     "Print the contents of the registers\n");
	printf("   i(Mem <b <n>>  "\
	     "Print n iMem locations starting at b\n");
	printf("   d(Mem <b <n>>  "\
	     "Print n dMem locations starting at b\n");
	printf("   t(race         "\
	     "Toggle instruction trace\n");
	printf("   p(rint         "\
	     "Toggle print of total instructions executed"\
	     " ('go' only)\n");
	printf("   c(lear         "\
	     "Reset simulator for new execution of program\n");
	printf("   h(elp          "\
	     "Cause this list of commands to be printed\n");
	printf("   q(uit          "\
	     "Terminate the simulation\n");
	  break;

	case 'p' :
	/***********************************/
	icountflag = !icountflag ;
	printf("Printing instruction count now ");
	if ( icountflag ) printf("on.\n"); else printf("off.\n");
	break;

	case 's' :
    /***********************************/
	      if (atEOL ())  
		  	stepcnt = 1;
	      else if (getNum ())
		  	stepcnt = abs(num);
	      else 
		  	printf("Step count?\n");
      break;

	case 'g' :
		stepcnt = 1;
	break;

	case 'r' :
    /***********************************/
	for (i = 0; i < NO_REGS; i++)
	{
		printf("%1d: %4d    ", i,reg[i]);
		if ((i % 4) == 3)
			printf ("\n");
	}
      break;

	case 'i' :
	/***********************************/
		printcnt = 1 ;
		if ( getNum ())
		{ 
			iloc = num ;
			if ( getNum ()) 
				printcnt = num ;
		}
		if (!atEOL ())
			printf ("Instruction locations?\n");
		else
		{ 
			while ((iloc >= 0) && (iloc < IADDR_SIZE) && (printcnt > 0) )
			{ 
				writeInstruction(iloc);
				iloc++ ;
				printcnt-- ;
			}
		}
	break;

	case 'd' :
	/***********************************/
		printcnt = 1 ;
		if ( getNum  ())
		{
			dloc = num ;
			if ( getNum ()) 
				printcnt = num ;
		}
		if (!atEOL ())
			printf("Data locations?\n");
		else
		{
			while ((dloc >= 0) && (dloc < DADDR_SIZE)&& (printcnt > 0))
			{
				printf("%5d: %5d\n",dloc,dMem[dloc]);
				dloc++;
				printcnt--;
			}
		}
	break;

	case 'c' :
	/***********************************/
	iloc = 0;
	dloc = 0;
	stepcnt = 0;

	for (regNo = 0;  regNo < NO_REGS ; regNo++)
	    reg[regNo] = 0 ;
	dMem[0] = DADDR_SIZE - 1 ;

	for (loc = 1 ; loc < DADDR_SIZE ; loc++)
	    dMem[loc] = 0 ;
	break;

	case 'q' : return FALSE;  /* break; */

	default : 
		printf("Command %c unknown.\n", cmd); break;
  }  /* case */
	stepResult = srOKAY;
	if ( stepcnt > 0 )
	{
		if ( cmd == 'g' )
		{
			stepcnt = 0;
			while (stepResult == srOKAY)
			{
				iloc = reg[PC_REG] ;
				if ( traceflag ) 
					writeInstruction( iloc ) ;
				stepResult = stepYVM ();
				stepcnt++;
			}
			if ( icountflag )
				printf("Number of instructions executed = %d\n",stepcnt);
		}
		else
		{
			while ((stepcnt > 0) && (stepResult == srOKAY))
			{ 
				iloc = reg[PC_REG] ;
				if ( traceflag ) 
					writeInstruction( iloc ) ;
				stepResult = stepYVM ();
				stepcnt-- ;
			}
		}
		printf( "%s\n",stepResultTab[stepResult] );
		}
	return TRUE;
} /* doCommand */


/********************************************/
/* E X E C U T I O N   B E G I N S   H E R E */
/********************************************/

main( int argc, char *argv[] )
{
	if (argc != 2)
	{ 
		printf("Usage: %s <filename>\n",argv[0]);
		exit(1);
	}

	strcpy(pgmName,argv[1]) ;
/* File Extension */
	if (strchr (pgmName, '.') == NULL)
		strcat(pgmName,".vm");

	pgm = fopen(pgmName,"r");

	if (pgm == NULL)
	{
		printf("file '%s' not found\n",pgmName);
		exit(1);
	}

	/* read the program */
	if (!readInstructions ())
		exit(1) ;
	/* switch input file to terminal */
	/* reset( input ); */
	/* read-eval-print */
	printf("YVM  simulation (enter h for help)...\n");
	do {
		done = !doCommand ();
	} while (! done );
	printf("Simulator terminated.\n");

	return 0;
}
